/***********************************************************************
 FrameBuffer.cpp - Class to manage in-memory frame buffer using libsdl
 	and related libraries, for building the slate bitmap.

 Created 2006.06.10 by Warren Young

 Copyright (c) 2006 by Pixel Corps.  This program may be used under
 the terms of the X11 license, a copy of which should have accompanied
 this program, in the LICENSE file.
***********************************************************************/

#include "FrameBuffer.h"

#include "makepng.h"
#include "maketiff.h"

#include <SDL.h>
#include <SDL_gfxPrimitives.h>
#include <SDL_image.h>

#include <stdexcept>
#include <iostream>

#include <ctype.h>

using namespace std;


////////////////////////////////////////////////////////////////////////
// Constants

//// Slate layout parameters -- notice that most of these values are
//// relative to the slate size, so that if we change that, the rest
//// should continue to be reasonable values.
// Slate size
static const int kSlateWidth = 960;
static const int kSlateHeight= 540;

// Location of logo
static const int kLogoX = kSlateWidth / 20;
static const int kLogoY = kSlateHeight / 20;

// Parameters of main text area
static const int kTextGutterX = 3 * kSlateWidth / 7;
static const int kTextGutterW = 10;
static const int kTextStartY = kSlateHeight / 4;
static const int kTextMarginRight = kSlateWidth / 30;
static const int kTextWidth = kSlateWidth - kTextGutterX -
		kTextGutterW - kTextMarginRight;

// Height of text lines.
static const int kTextHeight = kSlateHeight / 22;
static const int kLineSpacing = kTextHeight / 3;
static const int kLineHeight = kTextHeight + kLineSpacing;

//// Other constants
// File name of the TTF fonts we use.
static const char* kpcRegularFontFileName = "fonts/Vera.ttf";
static const char* kpcBoldFontFileName = "fonts/VeraBd.ttf";


//// ctor //////////////////////////////////////////////////////////////

FrameBuffer::FrameBuffer() :
pSurface_(0),
nLines_(0),
nGroups_(0)
{
	// Start SDL
	setenv("SDL_VIDEODRIVER", "dummy", 0);
	if ((SDL_Init(SDL_INIT_VIDEO) == -1)) { 
		Error("Could not initialize SDL");
		throw runtime_error("FrameBuffer init");
	}
	atexit(SDL_Quit);
	SDL_ShowCursor(SDL_DISABLE);

	// Initialize TTF library
	if (TTF_Init() != 0) {
		throw runtime_error("init TTF library");
	}

	// Load fonts
	pRegularFont_ = TTF_OpenFont(kpcRegularFontFileName, kTextHeight);
	pBoldFont_ = TTF_OpenFont(kpcBoldFontFileName, kTextHeight);
	if (!pRegularFont_ && !pBoldFont_) {
		throw runtime_error("load TTF fonts");
	}

	// Create drawing surface
	if (!(pSurface_ = SDL_SetVideoMode(kSlateWidth, kSlateHeight, 24,
			SDL_SWSURFACE))) {
		Error("Unable to obtain SDL drawing surface");
		throw runtime_error("FrameBuffer init");
	}

	// Clear drawing surface
	boxColor(Surface(), 0, 0, kSlateWidth - 1, kSlateHeight - 1,
			RGB(44, 44, 44));
	rectangleColor(Surface(), 0, 0, kSlateWidth - 1, kSlateHeight - 1,
			RGB(44, 44, 44));

	// Drop logo onto surface
	CompositePNG("logo.png", kLogoX, kLogoY);
}


//// CompositePNG //////////////////////////////////////////////////////
	
bool
FrameBuffer::CompositePNG(const char* pcFileName, int x, int y)
{
	SDL_Surface* image = IMG_Load(pcFileName);
	if (image) {
		SDL_Rect rcDest = { x, y, 0, 0 };
		SDL_BlitSurface(image, 0, Surface(), &rcDest);
		SDL_FreeSurface(image);		
		return true;
	}
	else {
		cerr << "Failed to load PNG for composite: " <<
				IMG_GetError() << endl;
		return false;
	}
}


//// Error /////////////////////////////////////////////////////////////
// Display a simple SDL error

void
FrameBuffer::Error(const char* prefix)
{
	cerr << prefix << ": " << SDL_GetError() << endl;
}


//// Lock //////////////////////////////////////////////////////////////
// Locks the frame buffer, if need be.

void
FrameBuffer::Lock()
{
	if (SDL_MUSTLOCK(Surface())) {
		SDL_LockSurface(Surface());
	}
}


//// PixelFormat ///////////////////////////////////////////////////////
// Return the drawing surface's pixel format info, or 0 if it isn't
// available.

SDL_PixelFormat*
FrameBuffer::PixelFormat()
{
	if (Surface()) {
		return Surface()->format;
	}
	else {
		return 0;
	}
}


//// RGB ///////////////////////////////////////////////////////////////
// Return a pixel value appropriate to the drawing surface's mode,
// given the raw RGB values.

Uint32
FrameBuffer::RGB(int r, int g, int b)
{
	return ((r << 24) | (g << 16) | (b << 8) | 0xFF);
}


//// Update ////////////////////////////////////////////////////////////
// Push all pending updates to the global drawing surface.

void
FrameBuffer::Update()
{
	SDL_UpdateRect(Surface(), 0, 0, 0, 0);
}


//// Unlock ////////////////////////////////////////////////////////////
// Unlocks the frame buffer, if it needs to be unlocked.

void
FrameBuffer::Unlock()
{
	if (SDL_MUSTLOCK(Surface())) {
		SDL_UnlockSurface(Surface());
	}
}


//// WordEnd ///////////////////////////////////////////////////////////
// Return pointer to space or null character following the word
// following the text at the pointer.

const char*
FrameBuffer::WordEnd(const char* pc)
{
	if (*pc == '\0') {
		return 0;
	}

	while (*pc && isspace(*pc)) {
		++pc;
	}

	const char* pc2 = strchr(pc, ' ');
	if (pc2) {
		return pc2;
	}
	else {
		return strchr(pc, '\0');
	}
}


//// WrapText //////////////////////////////////////////////////////////

void
FrameBuffer::WrapText(const char* pcText, vector<string>* pLines)
{
	int w;
	const char* pcSpace = pcText;
	const char* pcLineEnd = 0;
	while ((pcSpace = WordEnd(pcSpace)) != 0) {
		string s(pcText, pcSpace - pcText);
		TTF_SizeText(pRegularFont_, s.c_str(), &w, 0);
		if (w < kTextWidth) {
			pcLineEnd = pcSpace;
		}
		else {
			pLines->push_back(string(pcText, pcLineEnd - pcText));
			pcText = pcLineEnd;
			while (*pcText && isspace(*pcText)) {
				++pcText;
			}
			pcSpace = pcText;
		}
	}

	pLines->push_back(pcText);
}


//// WriteImage ////////////////////////////////////////////////////////

bool
FrameBuffer::WriteImage(const char* pcFileName, ImageType eType)
{
	string sFileName(pcFileName);
	if (eType == kImage_PNG) {
		sFileName.append(".png");
		return SaveSurfaceAsPNG(Surface(), sFileName.c_str());
	}
	else {
		sFileName.append(".tif");
		return SaveSurfaceAsTIFF(Surface(), sFileName.c_str());
	}
}


//// WriteText /////////////////////////////////////////////////////////

bool
FrameBuffer::WriteText(const char* pcLabel, const char* pcText)
{
	SDL_Color FGColor = { 255, 255, 255, 0 };
	int nTextY = kTextStartY + (nLines_ * kLineHeight) +
			((nGroups_ + 1) * kLineSpacing);

	// Build and blit label
	SDL_Surface* pTextSurface = TTF_RenderText_Blended(pBoldFont_,
			pcLabel, FGColor);
	SDL_Rect Dest1 = {
			kTextGutterX - kTextGutterW - pTextSurface->w, nTextY,
			pTextSurface->w, pTextSurface->h };
	SDL_BlitSurface(pTextSurface, 0, Surface(), &Dest1);
	SDL_FreeSurface(pTextSurface);

	// Build and blit regular text
	SDL_Rect Dest2 = {
			kTextGutterX + kTextGutterW, nTextY,
			pTextSurface->w, pTextSurface->h };
	vector<string> lines;
	WrapText(pcText, &lines);
	vector<string>::const_iterator it;
	for (it = lines.begin(); it != lines.end(); ++it) {
		pTextSurface = TTF_RenderText_Blended(pRegularFont_,
				it->c_str(), FGColor);
		SDL_BlitSurface(pTextSurface, 0, Surface(), &Dest2);
		SDL_FreeSurface(pTextSurface);
		Dest2.y += kLineHeight;
		++nLines_;
	}

	// Advance vertically
	++nGroups_;

	return true;
}

